package android_board;

import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.text.InputType;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
import android_codebehind.GUIPlayer;
import android_fields.AndroidField;
import android_fields.GUIOwnable;
import android_fields.GUIStreet;
import common.Car;
import common.GUIColor;
import dk.dtu.compute.course02312.matadordel2.MainActivity;
import dk.dtu.compute.course02312.matadordel2.R;

// import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;

public class AndroidGUIBoardController {
	
	GUIBoard board;
	private static volatile Random rand = null;
	
	private static Random rand() {
		if(rand == null) {
			synchronized(AndroidGUIBoardController.class) {
				if(rand == null)
					rand = new Random();
			}
		}
		return rand;
	}
	
	/**
	 * Contains service methods for board for controlling the board.
	 */
	public AndroidGUIBoardController() {
		this.board = GUIBoard.getInstance();
		// makeCenter();
		
	}
	/**
	 * Displays a message to the user and awaits the response.<br>
	 * @param msg The message that promts the user.
	 * @param showInputField
	 * @param showDropDown
	 * @param items
	 * @param buttons
	 * @return The string that the user has entered.
	 */
	public String[] getInput(
		final String msg,
		final boolean showInputField,
		final boolean showDropDown,
		final String[] items,
		final String[] buttons) {
		
		if(buttons == null || buttons.length == 0) {
			throw new IllegalArgumentException(
				"At least one button is required - this method returns only when button is pressed");
		}
		if(showDropDown && (items == null || items.length == 0)) {
			throw new IllegalArgumentException("It is not possible to show drop-down without items");
		}
		
		final CountDownLatch latch = new CountDownLatch(1);
		final String[] res = new String[3];
		final int TEXT_RES = 0, BTN_RES = 1, DROPDOWN_RES = 2;
		
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				View[] v = AndroidGUIBoardController.this.board.getInputLayout();
				final RelativeLayout base = (RelativeLayout) v[0];
				final TextView tv = (TextView) v[1];
				tv.setText("" + msg);
				
				LinearLayout btnHolderLayout = new LinearLayout(MainActivity.context);
				btnHolderLayout.setOrientation(LinearLayout.HORIZONTAL);
				btnHolderLayout.setId(GUIBoard.generateViewId());
				
				// EditText
				final EditText et = new EditText(MainActivity.context);
				et.setId(GUIBoard.generateViewId());
				if(showInputField) {
					et.setInputType(InputType.TYPE_TEXT_VARIATION_PERSON_NAME);
					et.setWidth(300);
					et.setBackgroundColor(GUIColor.WHITE.getRGB());
					RelativeLayout.LayoutParams et_params =
						new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
					// et_params.setMargins(10, 10, 10, 10);
					et_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
					et_params.addRule(RelativeLayout.ABOVE, btnHolderLayout.getId());
					base.addView(et, et_params);
				}
				
				// Dropdown
				if(showDropDown) {
					ArrayAdapter<String> spinnerArrayAdapter =
						new ArrayAdapter<String>(MainActivity.context,
							android.R.layout.simple_spinner_item, items);
					spinnerArrayAdapter
						.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
					Spinner spinner = new Spinner(MainActivity.context);
					spinner.setAdapter(spinnerArrayAdapter);
					spinner.setId(GUIBoard.generateViewId());
					spinner.setBackgroundColor(GUIColor.WHITE.getRGB());
					RelativeLayout.LayoutParams spinner_params =
						new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
					spinner_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
					spinner_params.addRule(RelativeLayout.ABOVE, showInputField ? et.getId()
						: btnHolderLayout.getId());
					spinner.setOnItemSelectedListener(new OnItemSelectedListener() {
						@SuppressWarnings("unused")
						@Override
						public void onItemSelected(AdapterView<?> parent, View view, int pos,
							long id) {
							res[DROPDOWN_RES] = items[pos];
						}
						@SuppressWarnings("unused")
						@Override
						public void onNothingSelected(AdapterView<?> parent) {
							/*  */
						}
					});
					
					RelativeLayout bg = new RelativeLayout(MainActivity.context);
					base.addView(bg, spinner_params);
					bg.addView(spinner);
					
					ImageView corner = new ImageView(MainActivity.context);
					corner.setBackgroundResource(R.drawable.corner);
					RelativeLayout.LayoutParams corner_params =
						new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
					corner_params.addRule(RelativeLayout.ALIGN_RIGHT, spinner.getId());
					corner_params.addRule(RelativeLayout.ALIGN_BOTTOM, spinner.getId());
					int left = (int) (5.5 * GUIBoard.FIELDWIDTH);
					corner_params.setMargins(left, 0, 0, 0);
					bg.addView(corner, corner_params);
				}
				
				// Buttons
				for(int i = 0; i < buttons.length; i++) {
					final Button btn = new Button(MainActivity.context);
					btn.setId(GUIBoard.generateViewId());
					final int j = i;
					btn.setText(buttons[i]);
					btn.setBackgroundResource(R.drawable.button_drawable);
					
					OnClickListener endListener = new OnClickListener() {
						@SuppressWarnings("unused")
						@Override
						public void onClick(View v1) {
							hideKeyboard();
							tv.setText("");
							base.removeAllViews();
							RelativeLayout.LayoutParams tv_params =
								new RelativeLayout.LayoutParams(9 * GUIBoard.FIELDWIDTH,
									3 * GUIBoard.FIELDHEIGHT);
							base.addView(tv, tv_params);
							if(showInputField) res[TEXT_RES] = et.getText().toString();
							res[BTN_RES] = buttons[j];
							latch.countDown();
						}
					};
					btn.setOnClickListener(endListener);
					
					btnHolderLayout.addView(btn);
				}
				
				RelativeLayout.LayoutParams horizontal_params =
					new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
				horizontal_params.addRule(RelativeLayout.CENTER_HORIZONTAL);
				horizontal_params.addRule(RelativeLayout.ALIGN_BOTTOM, tv.getId());
				horizontal_params.setMargins(0, 15, 0, 15);
				base.addView(btnHolderLayout, horizontal_params);
				
			}
		});
		
		try {
			latch.await();
		} catch(InterruptedException e) {
			MainActivity.context.runOnUiThread(new Runnable() {
				@Override
				public void run() {
					Toast.makeText(MainActivity.context, "Something went wrong!",
						Toast.LENGTH_SHORT).show();
				}
			});
			e.printStackTrace();
		}
		return res;
	}
	
	/**
	 * Closes the GUI, so you can start a new one.
	 */
	public void closeGUI() {
		GUIBoard.destroy();
	}
	
	/**
	 * Sets the title of a field on the board.<br>
	 * @param fieldNumber
	 *        : int [1:40]
	 * @param title
	 *        : String (Mind the length!)
	 */
	public void setTitleText(final int fieldNumber, final String title) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				AndroidGUIBoardController.this.board.getField(fieldNumber).setTitle(title);
			}
		});
	}
	
	/**
	 * Sets the subText of a field on the board.<br>
	 * @param fieldNumber
	 *        : int [1:40]
	 * @param subText
	 *        : String (Mind the length!)
	 */
	public void setSubText(final int fieldNumber, final String subText) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				AndroidGUIBoardController.this.board.getField(fieldNumber).setSubText(subText);
			}
		});
	}
	
	/**
	 * Sets the Description (The text shown in the center when mouse hovers) of
	 * a field on the board.<br>
	 * @param fieldNumber
	 *        : int [1:40]
	 * @param description
	 *        : String (Mind the length!)
	 */
	public void setDescriptionText(final int fieldNumber, final String description) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				AndroidGUIBoardController.this.board.getField(fieldNumber).setDescription(
					description);
			}
		});
	}
	
	public void addPlayer(final String name, final int balance, final Car<Bitmap> car) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				AndroidGUIBoardController.this.board.addPlayer(name, balance, car);
			}
		});
	}
	
	/**
	 * Adds a player to the board.<br>
	 * A new player with the same color will replace the old.<br>
	 * Max. 6 players.<br>
	 * @param name
	 *        : String (Mind the length!)
	 * @param balance
	 *        : int<br>
	 *        Automatic coloring
	 */
	public void addPlayer(final String name, final int balance) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				AndroidGUIBoardController.this.board.addPlayer(name, balance);
			}
		});
	}
	
	/**
	 * Sets the balance of a player if the player has been added.<br>
	 * @param name
	 *        The name of the player
	 * @param newBalance
	 *        : int
	 */
	public void setBalance(String name, int newBalance) {
		GUIPlayer p = this.board.getPlayer(name);
		if(p != null) {
			p.setBalance(newBalance);
			MainActivity.context.runOnUiThread(new Runnable() {
				@Override
				public void run() {
					AndroidGUIBoardController.this.board.updatePlayers();
				}
			});
		}
	}
	
	/**
	 * Shows two dice on the board. The dice will have the specified values, but
	 * the placement is random.<br>
	 * @param faceValue1
	 *        : int [1:6]
	 * @param faceValue2
	 *        : int [1:6]<br>
	 *        (If a faceValue is out of bounds nothing will happen!)
	 */
	public void setDice(int faceValue1, int faceValue2) {
		int rotation1 = rand().nextInt(360);
		int rotation2 = rand().nextInt(360);
		setDice(faceValue1, rotation1, faceValue2, rotation2);
	}
	
	/**
	 * Shows two dice on the board. The dice will have the specified values and
	 * location.<br>
	 * @param faceValue1
	 *        : int [1:6]
	 * @param x1
	 *        : int [0:10]
	 * @param y1
	 *        : int [0:10]
	 * @param faceValue2
	 *        : int [1:6]
	 * @param x2
	 *        : int [0:10]
	 * @param y2
	 *        : int [0:10]
	 */
	public void setDice(int faceValue1, int x1, int y1, int faceValue2, int x2,
		int y2) {
		int rotation1 = rand().nextInt(360);
		int rotation2 = rand().nextInt(360);
		setDice(faceValue1, rotation1, x1, y1, faceValue2, rotation2, x2, y2);
	}
	
	/**
	 * Shows two dice on the board. The dice will have the specified values, but
	 * the placement is random.<br>
	 * @param faceValue1
	 *        : int [1:6]
	 * @param rotation1
	 *        : int [0:2]
	 * @param faceValue2
	 *        : int [1:6]
	 * @param rotation2
	 *        : int [0:2]<br>
	 *        (If a faceValue is out of bounds nothing will happen!)
	 */
	public void setDice(int faceValue1, int rotation1, int faceValue2,
		int rotation2) {
		// Make an accepted locations for a dice
		List<Point> dicePlaces = new ArrayList<Point>();
		for(int x = 1; x < 10; x++) {
			for(int y = 1; y < 10; y++) {
				if(x >= 4 && x <= 6 && y >= 4 && y <= 6) {
					continue;
				} // Do not add the points in the center.
					// if(x > 6 && y > (9 - this.board.getPlayerCount())) {
				if(x > 6 && y > (9 - 6)) {
					continue;
				} // Do not add the points used for the players names and
					// balances.
				dicePlaces.add(new Point(x, y));
			}
		}
		
		// Randomly choose a location for die1
		int index1 = 0;
		index1 = (int) (Math.random() * dicePlaces.size());
		Point dice1Position = dicePlaces.remove(index1);
		
		// Remove all locations "far" away from die1
		final int MAX_DISTANCE = 2;
		ArrayList<Point> toBeRemoved = new ArrayList<Point>();
		for(Point p : dicePlaces) {
			if(p.x < dice1Position.x - MAX_DISTANCE
				|| p.x > dice1Position.x + MAX_DISTANCE
				|| p.y < dice1Position.y - MAX_DISTANCE
				|| p.y > dice1Position.y + MAX_DISTANCE) {
				toBeRemoved.add(p);
			}
		}
		dicePlaces.removeAll(toBeRemoved);
		
		// Randomly choose a location for die2
		int index2 = 0;
		index2 = (int) (Math.random() * dicePlaces.size());
		Point dice2Position = dicePlaces.get(index2);
		
		setDice(faceValue1, rotation1, dice1Position.x, dice1Position.y,
			faceValue2, rotation2, dice2Position.x, dice2Position.y);
	}
	
	/**
	 * Shows two dice on the board. The dice will have the specified values,
	 * location and rotation.<br>
	 * @param faceValue1
	 *        : int [1:6]
	 * @param rotation1
	 *        : int [0:2]
	 * @param x1
	 *        : int [0:10]
	 * @param y1
	 *        : int [0:10]
	 * @param faceValue2
	 *        : int [1:6]
	 * @param rotation2
	 *        : int [0:2]
	 * @param x2
	 *        : int [0:10]
	 * @param y2
	 *        : int [0:10]
	 */
	public void setDice(final int faceValue1, final int rotation1, final int x1, final int y1,
		final int faceValue2, final int rotation2, final int x2, final int y2) {
		boolean faceValuesAreValid = areDiceFaceValuesValid(faceValue1,
			faceValue2);
		boolean rotationsAreValid = areDiceRotationsValid(rotation1, rotation2);
		boolean positionsAreValid = areDicePositionsValid(x1, y1, x2, y2);
		if(faceValuesAreValid && rotationsAreValid && positionsAreValid) {
			MainActivity.context.runOnUiThread(new Runnable() {
				@Override
				public void run() {
					AndroidGUIBoardController.this.board.setDice(x1, y1, faceValue1, rotation1, x2,
						y2,
						faceValue2, rotation2);
				}
			});
		}
	}
	
	private boolean areDicePositionsValid(int x1, int y1, int x2, int y2) {
		return x1 >= 0 && x1 <= 10 && y1 >= 0 && y1 <= 10 && x2 >= 0
			&& x2 <= 10 && y2 >= 0 && y2 <= 10;
	}
	private boolean areDiceRotationsValid(int rotation1, int rotation2) {
		return rotation1 >= 0 && rotation1 <= 359 && rotation2 >= 0
			&& rotation2 <= 359;
	}
	private boolean areDiceFaceValuesValid(int faceValue1, int faceValue2) {
		return faceValue1 >= 1 && faceValue1 <= 6 && faceValue2 >= 1
			&& faceValue2 <= 6;
	}
	
	public void displayChanceCard(final String txt) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				GUICenter.getInstance().setChanceCard(txt);
				GUICenter.getInstance().displayChanceCard();
			}
		});
	}
	public void setChanceCard(final String txt) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				GUICenter.getInstance().setChanceCard(txt);
			}
		});
	}
	public void displayChanceCard() {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				GUICenter.getInstance().displayChanceCard();
			}
		});
	}
	/**
	 * Places a car on the field.<br>
	 * All cars can be placed on the same field.<br>
	 * A car can only be placed if the corresponding player has been added.<br>
	 * If a car is placed on the same field multiple times, nothing more
	 * happens.<br>
	 * A car can not be placed on multiple fields simultaneously.
	 * @param fieldNumber
	 *        : int [1:40]
	 * @param name
	 *        The name of the player
	 */
	public void setCar(final int fieldNumber, final String name) {
		// removeCar(name);
		if(this.board.getPlayer(name) != null) {
			MainActivity.context.runOnUiThread(new Runnable() {
				@Override
				public void run() {
					AndroidGUIBoardController.this.board.getField(fieldNumber).setCar(name, true);
				}
			});
		}
	}
	
	/**
	 * Removes a car from the board.<br>
	 * If the car is not on the board, nothing happens.<br>
	 * @param fieldNumber
	 *        : int [1:40]
	 * @param name
	 *        The name of the player
	 */
	public void removeCar(final int fieldNumber, final String name) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				AndroidGUIBoardController.this.board.getField(fieldNumber).setCar(name, false);
				GUICenter.getInstance().displayDefault();
			}
		});
	}
	
	/**
	 * Removes all the players cars from the board.<br>
	 * If the car is not on the board, nothing happens.<br>
	 * @param name
	 *        The name of the player
	 */
	public void removeAllCars(final String name) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				for(int i = 1; i < 41; i++)
					AndroidGUIBoardController.this.board.getField(i).setCar(name, false);
				GUICenter.getInstance().displayDefault();
			}
		});
	}
	
	/**
	 * Sets an owner of a field.<br>
	 * The field border will have the same color as the player. The owners name
	 * will be printed in the subText. If the field is not a street, shipping or
	 * a brewery nothing happens.<br>
	 * If the owner hasn't been added to the board, nothing happens.
	 * @param fieldNumber
	 *        : int [1:40]
	 * @param prefix
	 *        The text to put in front of the players name on the
	 *        description of a field that the player owns.
	 * @param name
	 *        The name of the player
	 * @param postfix
	 *        The text to put after of the players name on the description
	 *        of a field that the player owns.
	 */
	public void setOwner(final int fieldNumber, final String prefix, final String name,
		final String postfix) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				AndroidField f = AndroidGUIBoardController.this.board.getField(fieldNumber);
				GUIPlayer p = AndroidGUIBoardController.this.board.getPlayer(name);
				if((f instanceof GUIOwnable) && p != null) {
					((GUIOwnable) f).setOwner(prefix, p, postfix);
				}
			}
		});
	}
	
	/**
	 * Removes an owner from the field.<br>
	 * If the field has no owner, nothing happens.
	 * @param fieldNumber
	 *        : int [1:40]
	 */
	public void removeOwner(final int fieldNumber) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				AndroidField f = AndroidGUIBoardController.this.board.getField(fieldNumber);
				if(f instanceof GUIOwnable) {
					((GUIOwnable) f).setOwner("", null, "");
				}
			}
		});
	}
	
	/**
	 * Sets houses from the street, and removes the hotel if one is present.<br>
	 * If houseCount is out of bounds, nothing happens.<br>
	 * If the field is not a street, nothing happens.<br>
	 * @param fieldNumber
	 *        : int [1:40]
	 * @param houseCount
	 *        : int [0:4]
	 */
	public void setHouses(final int fieldNumber, final int houseCount) {
		if(houseCount >= 0 && houseCount < 5) {
			MainActivity.context.runOnUiThread(new Runnable() {
				@Override
				public void run() {
					AndroidField f = AndroidGUIBoardController.this.board.getField(fieldNumber);
					if(f instanceof GUIStreet) {
						GUIStreet s = ((GUIStreet) f);
						s.setHouses(houseCount);
					}
				}
			});
		}
	}
	
	/**
	 * Sets whether or not a hotel should be on the street and removes all
	 * houses if any is present.<br>
	 * @param fieldNumber
	 *        : int [1:40]
	 * @param hasHotel
	 *        : boolean
	 */
	public void setHotel(final int fieldNumber, final boolean hasHotel) {
		MainActivity.context.runOnUiThread(new Runnable() {
			@Override
			public void run() {
				AndroidField f = AndroidGUIBoardController.this.board.getField(fieldNumber);
				if(f instanceof GUIStreet) {
					GUIStreet s = ((GUIStreet) f);
					s.setHotel(hasHotel);
				}
			}
		});
	}
	
	void hideKeyboard() {
		InputMethodManager mgr =
			(InputMethodManager) MainActivity.context
				.getSystemService(Context.INPUT_METHOD_SERVICE);
		try {
			if((mgr != null) && (MainActivity.context.getCurrentFocus() != null))
			{
				mgr.hideSoftInputFromWindow(
					MainActivity.context.getCurrentFocus().getWindowToken(), 0);
			}
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
}
